﻿/*
 * EdgeGeometryRenderer
 * 
 * initial implementation : 20.10.2008
 *
 * Copyright (c) Adam Sida, asida@centrum.cz.
 * All rights reserved.
 * 
 * for license see Petra.Gis.License.txt
 */
using System;
using System.IO;
//using System.Collections.Generic;
using System.Drawing;
//using System.Drawing.Drawing2D;
//using Petra.Gis.Drawing;
//using Petra.Gis.Geometry;
using Petra.Gis.Geometry.Support;
using Petra.Gis.RenderingServices.PlanarBase;

namespace Petra.Gis.RenderingServices.GDIPlus
{

  /// <summary>
  /// represents geometry renderer that implements specific function
  /// for rendering edge geometries eg. polygon, polyline, arc
  /// </summary>
  public abstract class EdgeGeomRenderer : BitmapMapRendererBase
  {


    protected Point[] outPts;
    protected Pen _outpen;
    protected int _PointIndex; //pointer to last output point

    //protected int xleft, xright, ybottom, ytop;
    protected bool hasOutput;



    /// <summary>
    /// xaml constr.
    /// </summary>
    protected EdgeGeomRenderer()
      :base()
    {
      outPts = new Point[100000];

      //
    }





    //protected EdgeGeomRenderer(BitmapMap map, RenderStyle s)
    //  : base(map, s)
    //{
    //  outPts = new Point[100000];

    //  // init vars..
    //  //xleft = 0;
    //  //ybottom = 0;
    //  //xright = (int)_map.Width;
    //  //ytop = (int)_map.Height;

    //  //Point tmpPt = map.RenderTransform.ProjectionToMap(map.ProjectionExtent.Left, map.ProjectionExtent.Bottom);
    //  //xleft = tmpPt.X;
    //  //ybottom = tmpPt.Y;

    //  //tmpPt = map.RenderTransform.ProjectionToMap(map.ProjectionExtent.Right, map.ProjectionExtent.Top);
    //  //xright = tmpPt.X;
    //  //ytop = tmpPt.Y;
    //}





    /// <summary>
    /// process 3 operations in one step:
    /// change geometry spatial coordinates to render/screen coordinates
    /// remove duplicate verticies
    /// polygon clip (LiangBarsky)
    /// </summary>
    /// <param name="buffer">byte[] with geometry coordinates</param>
    /// <param name="start">start index in buffer</param>
    /// <param name="numPoints">number of points in array</param>
    /// <param name="dim">dimension of geometry (2d, 3d)</param>
    protected bool geometryToPoints(byte[] buffer, int start, int numPoints, Dimension dim)
    {
      int numOfEdges = numPoints - 1;
      int ims, ime;

      hasOutput = false;

      Point startPt;// = new Point();
      Point endPt;// = new Point();
      //Point[] retPts;

      // if necessary resize array
      // for compound geometries array must be as long as many component points
      if (_PointIndex + numPoints > outPts.Length)
        Array.Resize<Point>(ref outPts, _PointIndex + numPoints);

      // prepare binary reader and read coordinates from byte[]
      MemoryStream m = new MemoryStream(buffer);
      m.Position = start;
      BinaryReader b = new BinaryReader(m);

      // read and transform edge start point
      startPt = ((DisplayTransformMatrix)((BitmapMap)rendererMap).RenderTransform).ProjectionToMap(b.ReadDouble(), b.ReadDouble());
      //skip z (h)
      if (dim == Dimension.dim3D)
        b.ReadDouble();

      // for all edges
      for (int i = 0; i < numOfEdges; i++)
      {
        // read and transform edge end point
        endPt = ((DisplayTransformMatrix)((BitmapMap)rendererMap).RenderTransform).ProjectionToMap(b.ReadDouble(), b.ReadDouble());
        // skip z (h)
        if (dim == Dimension.dim3D)
          b.ReadDouble();
         
        // if points not equal, test their zones
        // otherwise skip this edge
        if (startPt != endPt)
        {
          // get zone mask for both points
          ims = getPointIntesectMask(startPt);
          ime = getPointIntesectMask(endPt);

          // if both inside of MapRegion
          // put both to output
          if ((ims | ime) == 0)
          {
            if (_PointIndex == 0)
              addPointToArray(startPt.X, startPt.Y );

            addPointToArray(endPt.X, endPt.Y);
            hasOutput = true;
          }
          // ok, try if one inside and one outside (1 intersection) or
          // none inside but different zones, can have 1-2 intersection points
          // on edge of viewPort
          //else if (((ims | ime) != 0 && (ims == 0 || ime == 0)) ||
          //          ((ims | ime) != 0 && ims != 0 && ime != 0))
          else if ((ims & ime) != 0 && ims != ime)
          {
            // no intersection, goes around, must compute verticies on edge
            computeEdgePoints(ims, ime);
          }
          else if (ims != ime && (ims & ime) == 0)
          {
            // ok worst case, can intersect any edge
            // try clip first
            //addViewPortIntersectionPoints(outPts, ref _PointIndex, startPt.X, startPt.Y, endPt.X, endPt.Y);
            if (clipEdge2d(ims, startPt.X, startPt.Y, endPt.X, endPt.Y))
              hasOutput = true;
            else
              // no clipping goes outside around corner
              computeCrossCornerPoints(ims, ime);

          }
        }
        startPt = endPt;
      }

      return hasOutput;

    }






    /// <summary>
    /// liang Barsky line clip, automatically append intersection points to render array
    /// </summary>
    /// <param name="x1">start point x</param>
    /// <param name="y1">start point y</param>
    /// <param name="x2">end point x</param>
    /// <param name="y2">end point y</param>
    /// <returns>true if input line was clipped</returns>
    protected bool clipEdge2d(int startPtMask, int x1, int y1, int x2, int y2)
    {

      double u1 = 0;
      double u2 = 1;
      int Dx = x2 - x1;
      int Dy;

      if (cliptest(-Dx, (x1 - xleft), ref u1, ref u2))
      {
        if (cliptest(Dx, xright - x1, ref u1, ref u2))
        {
          Dy = y2 - y1;
          if (cliptest(-Dy, (y1 - ybottom), ref u1, ref u2))
          {
            if (cliptest(Dy, (ytop - y1), ref u1, ref u2))
            {

              //if (u1 <= 0 && u2 >= 1)
              //{ 
                
              //}

              if (u1 > 0)
              {
                //x1 = x1 + (int)(u1 * Dx);
                //y1 = y1 + (int)(u1 * Dy);
                if (startPtMask == 0 && _PointIndex == 0)
                  addPointToArray(x1, y1);

                addPointToArray(x1 + (int)(u1 * Dx), y1 + (int)(u1 * Dy));
              }
              if (u2 < 1)
              {
                //x2 = x1 + (int)(u2 * Dx);
                //y2 = y1 + (int)(u2 * Dy);

                addPointToArray(x1 + (int)(u2 * Dx), y1 + (int)(u2 * Dy));
              }
              else if (startPtMask != 0)
                addPointToArray(x2, y2);
              //return new Rectangle(x1, y1, x2, y2);
              return true;

            }
          }
        }
      }
      return false;
    }





    private static bool cliptest(int p, int q, ref double u1, ref double u2)
    {
      double r;
      bool result = true;

      if (p < 0)
      {
        r = (q * 1.0) / (p * 1.0);
        if (r > u2)
          result = false;
        else if (r > u1)
          u1 = r;
      }
      else if (p > 0)
      {
        r = (q * 1.0) / (p * 1.0);
        if (r < u1)
          result = false;
        else if (r < u2)
          u2 = r;
      }
      else if (q < 0)
        result = false;

      return result;
    }





    protected void computeEdgePoints(int ims, int ime)
    {

      // double check..
      if ((ims & ime) == 0)
        return;

      switch (ims | ime)
      {
        case 10: //left top corner
          addPointToArray(xleft, ybottom);
          break;

        case 6: //left bottom corner
          addPointToArray(xleft, ytop);
          break;

        case 5: //right bottom corner
          addPointToArray(xright, ytop);
          break;

        case 9: //right top corner
          addPointToArray(xright, ybottom);
          break;

        case 13:
          addPointToArray(xright, ybottom);
          addPointToArray(xright, ytop);
          break;

        case 14:
          addPointToArray(xleft, ytop);
          addPointToArray(xleft, ybottom);
          break;

        case 11:
          addPointToArray(xleft, ybottom);
          addPointToArray(xright, ybottom);
          break;

        case 7:
          addPointToArray(xleft, ytop);
          addPointToArray(xright, ytop);
          break;

        default:
          throw new NotImplementedException();
      }

      //addPointToArray(xleft, ybottom);
      //addPointToArray(xleft, ytop);
      //addPointToArray(xright, ybottom);
      //addPointToArray(xright, ytop);



    }




    protected void computeCrossCornerPoints(int ims, int ime)
    {

      // double check..
      if (!(ims != ime && (ims & ime) == 0))
        return;


      switch (ims | ime)
      {
        //case 10:
        //  addPointToArray(xleft, ybottom);
        //  break;
        //case 6:
        //  addPointToArray(xleft, ytop);
        //  break;

        case 5: //ok right bottom corner
          addPointToArray(xright, ytop);
          break;

        //case 1:
        //  addPointToArray(xright, ybottom);
        //  break;

        case 9: //right top corner
          addPointToArray(xright, ybottom);
          break;

        default:
          throw new NotImplementedException();
      }


    }




    //protected int _lastPointIndex = 0;

    /// <summary>
    /// function that appends points to render output array 
    /// </summary>
    /// <param name="x">point coordinate</param>
    /// <param name="y">point coordinate</param>
    protected void addPointToArray(int x, int y)
    {
      // resize array if necessary
      if (_PointIndex == outPts.Length)
        Array.Resize<Point>(ref outPts, outPts.Length + 4);

      if (_PointIndex > 0)
      {
        if (outPts[_PointIndex - 1].X != x || outPts[_PointIndex - 1].Y != y)
          outPts[_PointIndex++] = new Point(x, y);
      }
      else
        outPts[_PointIndex++] = new Point(x, y);
    }





    ///// <summary>
    ///// function that appends points to render output array 
    ///// </summary>
    ///// <param name="x">point coordinate</param>
    ///// <param name="y">point coordinate</param>
    //protected void addUncheckPointToArray(int x, int y)
    //{
    //  // resize array if necessary
    //  if (_PointIndex == outPts.Length)
    //    Array.Resize<Point>(ref outPts, outPts.Length + 4);

    //  if (_PointIndex > 0)
    //  {
    //    if (outPts[_PointIndex - 1].X != x || outPts[_PointIndex - 1].Y != y)
    //      outPts[_PointIndex++] = new Point(x, y);
    //  }
    //  else
    //    outPts[_PointIndex++] = new Point(x, y);
    //}






    protected void resetPointArray()
    {
      _PointIndex = 0;
      //Array.Clear(outPts, 0, outPts.Length);
    }




  }
}
